package com.github.wxiaoqi.security.gate.jwt;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

import com.github.wxiaoqi.security.common.exception.UpBussnissException;
import com.unipock.pay.vo.gate.ClientInfo;
import com.unipock.pay.vo.user.UserInfo;

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Component
public class JwtTokenUtil implements Serializable {

	Logger logger = LoggerFactory.getLogger(JwtTokenUtil.class);
	private static final long serialVersionUID = -3301605591108950415L;

	private static final String CLAIM_KEY_USERNAME = "sub";
	private static final String CLAIM_KEY_PHONE = "phone";
	private static final String CLAIM_KEY_CREATED = "created";
	private static final String CLAIM_KEY_ROLE = "role";

	@Value("${gate.jwt.secret}")
	private String secret;

	@Value("${gate.jwt.expiration}")
	private Long expiration;

	@Autowired
	private RedisTemplate<String, Object> redisTemplate;
	//
	// @Value("${gate.jwt.prefix}")
	// private String prefix;

	public String getPhoneFromToken(String token) {
		String username;
		try {
			final Claims claims = getClaimsFromToken(token);
			username = claims.get(CLAIM_KEY_PHONE,String.class);
		} catch (Exception e) {
			username = null;
		}
		return username;
	}
	
	public Date getCreatedDateFromToken(String token) {
		Date created;
		try {
			final Claims claims = getClaimsFromToken(token);
			created = new Date((Long) claims.get(CLAIM_KEY_CREATED));
		} catch (Exception e) {
			created = null;
		}
		return created;
	}

	public Date getExpirationDateFromToken(String token) {
		Date expiration;
		try {
			final Claims claims = getClaimsFromToken(token);
			expiration = claims.getExpiration();
		} catch (Exception e) {
			expiration = null;
		}
		return expiration;
	}

	public Boolean invalid(String token) {
		String username = this.getPhoneFromToken(token);
		return redisTemplate.opsForValue().setIfAbsent(username, null);
	}

	private Claims getClaimsFromToken(String token) {
		Claims claims;
		try {
			claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
		} catch (Exception e) {
			logger.error("解密token错误",e);
			claims = null;
			throw new UpBussnissException("解密token错误",e);
		}
		return claims;
	}

	private Date generateExpirationDate() {
		return new Date(System.currentTimeMillis() + expiration * 1000);
	}

	public Boolean isTokenExpired(String token) {
		final Date expiration = getExpirationDateFromToken(token);
		return expiration.before(new Date());
	}

	private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
		return (lastPasswordReset != null && created.before(lastPasswordReset));
	}

	public String generateToken(UserInfo info) {
		Object old = redisTemplate.opsForValue().get(info.getPhone());
		String token = "";
		if (old==null||isTokenExpired(old.toString())) {
			Map<String, Object> claims = new HashMap<String, Object>();
			claims.put(CLAIM_KEY_ROLE, info.getRoles());
			claims.put(CLAIM_KEY_PHONE, info.getPhone());
			claims.put(CLAIM_KEY_CREATED, new Date());
			token = generateToken(claims);
			redisTemplate.opsForValue().set(info.getPhone(), token, expiration, TimeUnit.SECONDS);
		} else {
			token = old.toString();
		}
		return token;
	}

	String generateToken(Map<String, Object> claims) {
		return Jwts.builder().setClaims(claims).setExpiration(generateExpirationDate())
				.signWith(SignatureAlgorithm.HS512, secret).compact();
	}

	public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
		final Date created = getCreatedDateFromToken(token);
		return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset) && !isTokenExpired(token);
	}

	public String refreshToken(String token) {
		String refreshedToken;
		try {
			final Claims claims = getClaimsFromToken(token);
			claims.put(CLAIM_KEY_CREATED, new Date());
			refreshedToken = generateToken(claims);
			// 延长token的有效期
			redisTemplate.opsForValue().set(claims.get(CLAIM_KEY_PHONE,String.class), refreshedToken, expiration, TimeUnit.SECONDS);
		} catch (Exception e) {
			logger.error("延长token使用时间失败.",e);
			refreshedToken = null;
		}
		return refreshedToken;
	}

	public Boolean validateToken(String token, UserInfo info) {
		if (StringUtils.isBlank(token)) {
			return false;
		}
		Object existToken = redisTemplate.opsForValue().get(info.getPhone());
		if (token.equals(existToken)) {
			final String username = getPhoneFromToken(token);
			final Date created = getCreatedDateFromToken(token);
			return (username.equals(info.getPhone()) && !isTokenExpired(token));
		} else {
			return false;
		}
		// if(!token.startsWith(prefix)){
		// return false;
		// }else {
		// token = token.substring(prefix.length() + 1);
		// }
	}

	public static void main(String[] args) {
		BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
		String pwd = encoder.encode("admin");
		System.out.println(pwd);
	}

	public boolean validateInredis(String token) {
		String phone = this.getPhoneFromToken(token);
		Object tokenInRedis = redisTemplate.opsForValue().get(phone);
		if(token.equals(tokenInRedis)){
			return true;
		}
		return false;
	}
}
